package com.zy.explore.core;

import org.springframework.util.Assert;

import java.security.SecureRandom;
import java.util.Random;

/**
 * description 生成 {@link ObjectId} 的工厂
 *
 * @author ZY
 * @date 2021-05-05 18:00
 */
public interface ObjectIdFactory {

    /**
     * 机器码和进程号全部都使用随机数，这个是最有用的，这可以避免 docker 进程号永远是 1 的问题。
     * <p>
     * 同时 docker 默认使用随机网卡内容，如果使用宿主机网卡信息，则网卡信息可能会重复
     *
     * @param random 产生随机数的随机器
     * @return 一个使用随机机器码和进程号的 {@link ObjectIdFactory}
     */
    static ObjectIdFactory random(Random random) {
        return custom(random.nextInt(0x1000000), (short) random.nextInt(0x8000), random.nextInt(0x1000000));
    }

    /**
     * 直接指定机器码、进程号和 counter 初始值
     *
     * @param machineId 机器码 hash 值，最高位字节必须是 0 ，也就是最多填充 3 个字节
     * @param processId 进程号 ID ，可以是 0-65535 ， java 中不存在无符号整数，所以超过 32767 的数表示为负数
     * @param idCounter counter 的初始值，不建议为 0 ， 0 会加大ID冲突机率，建议使用随机数
     * @return 由上面参数指定的 {@link ObjectIdFactory}
     * @throws IllegalArgumentException 当 {@code machineId} 或 {@code idCounter} 最高字节位不为 0 时抛出
     */
    static ObjectIdFactory custom(int machineId, short processId, int idCounter) {
        Assert.isTrue((machineId & 0xff000000) == 0, "machineId must fit in three bytes");
        Assert.isTrue((idCounter & 0xff000000) == 0, "idCounter must fit in three bytes");

        return new CustomObjectIdFactory(machineId, processId, idCounter);
    }

    ObjectId generate();

    /**
     * 内部类单例模式
     * 生成全局唯一、随机、默认 ObjectId 工厂
     * 如需定制化参数 请使用 {@link #custom(int, short, int)} 实例化多个工厂
     */
    class Default {

        private final static ObjectIdFactory INSTANCE = ObjectIdFactory.random(new SecureRandom());

        private Default() {
        }

        public static ObjectId generate() {
            return INSTANCE.generate();
        }
    }
}
